{
  lib,
  ...
}:

let
  configDir = "/var/lib/foobar";
in
{
  name = "home-assistant";
  meta.maintainers = lib.teams.home-assistant.members;

  nodes.hass =
    { pkgs, ... }:
    {
      services.postgresql = {
        enable = true;
        ensureDatabases = [ "hass" ];
        ensureUsers = [
          {
            name = "hass";
            ensureDBOwnership = true;
          }
        ];
      };

      services.home-assistant = {
        enable = true;
        inherit configDir;

        # provide dependencies through package overrides
        package = (
          pkgs.home-assistant.override {
            extraPackages =
              ps: with ps; [
                colorama
              ];
            extraComponents = [
              # test char-tty device allow propagation into the service
              "zha"
            ];
          }
        );

        # provide component dependencies explicitly from the module
        extraComponents = [
          "mqtt"
        ];

        # provide package for postgresql support
        extraPackages =
          python3Packages: with python3Packages; [
            psycopg2
          ];

        # test loading custom components
        customComponents = with pkgs.home-assistant-custom-components; [
          prometheus_sensor
          # tests loading multiple components from a single package
          spook
        ];

        # test loading lovelace modules
        customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
          mini-graph-card
        ];

        config = {
          homeassistant = {
            name = "Home";
            time_zone = "UTC";
            latitude = "0.0";
            longitude = "0.0";
            elevation = 0;
          };

          # configure the recorder component to use the postgresql db
          recorder.db_url = "postgresql://@/hass";

          # we can't load default_config, because the updater requires
          # network access and would cause an error, so load frontend
          # here explicitly.
          # https://www.home-assistant.io/integrations/frontend/
          frontend = { };

          # include some popular integrations, that absolutely shouldn't break
          knx = { };
          zha = { };

          # set up a wake-on-lan switch to test capset capability required
          # for the ping suid wrapper
          # https://www.home-assistant.io/integrations/wake_on_lan/
          switch = [
            {
              platform = "wake_on_lan";
              mac = "00:11:22:33:44:55";
              host = "127.0.0.1";
            }
          ];

          # test component-based capability assignment (CAP_NET_BIND_SERVICE)
          # https://www.home-assistant.io/integrations/emulated_hue/
          emulated_hue = {
            host_ip = "127.0.0.1";
            listen_port = 80;
          };

          # https://www.home-assistant.io/integrations/logger/
          logger = {
            default = "debug";
          };
        };

        # configure the sample lovelace dashboard
        lovelaceConfig = {
          title = "My Awesome Home";
          views = [
            {
              title = "Example";
              cards = [
                {
                  type = "markdown";
                  title = "Lovelace";
                  content = "Welcome to your **Lovelace UI**.";
                }
              ];
            }
          ];
        };
        lovelaceConfigWritable = true;
      };

      # Add blueprints next, because we want to test the installation of the default blueprints first
      specialisation.addBlueprints = {
        inheritParentConfig = true;
        configuration.services.home-assistant = {
          blueprints.automation = [
            (pkgs.fetchurl {
              url = "https://github.com/home-assistant/core/raw/2025.1.4/homeassistant/components/automation/blueprints/motion_light.yaml";
              hash = "sha256-4HrDX65ycBMfEY2nZ7A25/d3ZnIHdpHZ+80Cblp+P5w=";
            })
          ];
          blueprints.template = [
            "${pkgs.home-assistant.src}/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml"
          ];
        };
      };

      # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded.
      specialisation.differentName = {
        inheritParentConfig = true;
        configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home";
      };

      # Cause a configuration change that requires a service restart as we added a new runtime dependency
      specialisation.newFeature = {
        inheritParentConfig = true;
        configuration.services.home-assistant.config.prometheus = { };
      };

      specialisation.removeCustomThings = {
        inheritParentConfig = true;
        configuration.services.home-assistant = {
          customComponents = lib.mkForce [ ];
          customLovelaceModules = lib.mkForce [ ];
          blueprints.automation = lib.mkForce [ ];
          blueprints.template = lib.mkForce [ ];
        };
      };
    };

  testScript =
    { nodes, ... }:
    let
      system = nodes.hass.system.build.toplevel;
    in
    ''
      import json

      start_all()


      def get_journal_cursor() -> str:
          exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
          assert exit == 0
          return json.loads(out)["__CURSOR"]


      def get_journal_since(cursor) -> str:
          exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service")
          assert exit == 0
          return out


      def get_unit_property(property) -> str:
          exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service")
          assert exit == 0
          return out


      def wait_for_homeassistant(cursor):
          hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")


      hass.wait_for_unit("home-assistant.service")
      cursor = get_journal_cursor()

      with subtest("Check that YAML configuration file is in place"):
          hass.succeed("test -L ${configDir}/configuration.yaml")

      with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
          hass.succeed("test -f ${configDir}/ui-lovelace.yaml")

      with subtest("Check that Home Assistant's web interface and API can be reached"):
          wait_for_homeassistant(cursor)
          hass.wait_for_open_port(8123)
          hass.succeed("curl --fail http://localhost:8123/lovelace")

      with subtest("Check that custom components get installed"):
          hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
          for integration in ("prometheus_sensor", "spook", "spook_inverse"):
              hass.wait_until_succeeds(f"journalctl -u home-assistant.service | grep -q 'We found a custom integration {integration} which has not been tested by Home Assistant'")

      with subtest("Check that lovelace modules are referenced and fetchable"):
          hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'")
          hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")

      with subtest("Check that optional dependencies are in the PYTHONPATH"):
          env = get_unit_property("Environment")
          python_path = env.split("PYTHONPATH=")[1].split()[0]
          for package in ["colorama", "paho-mqtt", "psycopg2"]:
              assert package in python_path, f"{package} not in PYTHONPATH"

      with subtest("Check that declaratively configured components get setup"):
          journal = get_journal_since(cursor)
          for domain in ["emulated_hue", "wake_on_lan"]:
              assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"

      with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
          hass.wait_for_open_port(80)
          hass.succeed("curl --fail http://localhost:80/description.xml")

      with subtest("Check extra components are considered in systemd unit hardening"):
          hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")

      with subtest("Check that default blueprints are copied writable"):
          hass.succeed("stat -c '%a' ${configDir}/blueprints/automation/homeassistant | grep 700")
          hass.succeed("stat -c '%a' ${configDir}/blueprints/automation/homeassistant/motion_light.yaml | grep 600")
          # Delete blueprints, so we can check the declarative setup next
          hass.execute("rm -rf ${configDir}/blueprints")

      with subtest("Check that configured blueprints are installed"):
          cursor = get_journal_cursor()
          hass.succeed("${system}/specialisation/addBlueprints/bin/switch-to-configuration test")
          wait_for_homeassistant(cursor)
          hass.succeed("test -L '${configDir}/blueprints/automation/motion_light.yaml'")
          hass.succeed("test -L '${configDir}/blueprints/template/inverted_binary_sensor.yaml'")

      with subtest("Check service restart from SIGHUP"):
          pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
          cursor = get_journal_cursor()
          hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
          wait_for_homeassistant(cursor)
          new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
          assert pid != new_pid, "The PID of the process must change after sending SIGHUP"

      with subtest("Check service restarts when dependencies change"):
          pid = new_pid
          cursor = get_journal_cursor()
          hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
          wait_for_homeassistant(cursor)
          new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
          assert pid != new_pid, "The PID of the process must change when its PYTHONPATH changess"

      with subtest("Check that new components get setup after restart"):
          journal = get_journal_since(cursor)
          for domain in ["prometheus"]:
              assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"

      with subtest("Check custom components, custom lovelace modules, and blueprints get removed"):
          cursor = get_journal_cursor()
          hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test")
          hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
          for integration in ("prometheus_sensor", "spook", "spook_inverse"):
              hass.fail(f"test -f ${configDir}/custom_components/{integration}/manifest.json")
          hass.fail("test -e '${configDir}/blueprints/automation/motion_light.yaml'")
          hass.fail("test -e '${configDir}/blueprints/template/inverted_binary_sensor.yaml'")
          wait_for_homeassistant(cursor)

      with subtest("Check that no errors were logged"):
          hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")

      with subtest("Check systemd unit hardening"):
          hass.log(hass.succeed("systemctl cat home-assistant.service"))
          hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
    '';
}
